<template>
  <textarea :placeholder="placeholder" ></textarea>
</template>

<script>
import * as CodeMirror from 'codemirror'
import 'codemirror/mode/sql/sql'
import 'codemirror/addon/hint/sql-hint'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/keymap/sublime'
import 'codemirror/addon/display/placeholder'

import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/lib/codemirror.css'

const defaultOption = {
  mode: { name: 'text/x-sql' },
  keymap: 'sublime',
  lineNumbers: true,
  lineWrapping: true,
  indentWidthTabs: true,
  smartIndent: true,
  matchBrackets: true,
  styleSelectedText: true,
  extraKeys: {
    'Ctrl-Enter': 'autocomplete',
    "'a'": completeAfter,
    "'b'": completeAfter,
    "'c'": completeAfter,
    "'d'": completeAfter,
    "'e'": completeAfter,
    "'f'": completeAfter,
    "'g'": completeAfter,
    "'h'": completeAfter,
    "'i'": completeAfter,
    "'j'": completeAfter,
    "'k'": completeAfter,
    "'l'": completeAfter,
    "'m'": completeAfter,
    "'n'": completeAfter,
    "'o'": completeAfter,
    "'p'": completeAfter,
    "'q'": completeAfter,
    "'r'": completeAfter,
    "'s'": completeAfter,
    "'t'": completeAfter,
    "'u'": completeAfter,
    "'v'": completeAfter,
    "'w'": completeAfter,
    "'x'": completeAfter,
    "'y'": completeAfter,
    "'z'": completeAfter,
    "'.'": completeAfter,
    "'='": completeIfInTag,
    Tab (cm) {
      const spaces = Array(cm.getOption('indentUnit') + 1).join(' ')
      cm.replaceSelection(spaces)
    }
  },
  theme: 'default'
}

function completeIfInTag (cm) {
  return completeAfter(cm, function () {
    var tok = cm.getTokenAt(cm.getCursor())
    if (tok.type === 'string' && (!/['"]/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length === 1)) return false
    var inner = CodeMirror.innerMode(cm.getMode(), tok.state).state
    return inner.tagName
  })
}
function completeAfter (cm, pred) {
  if (!pred || pred()) {
    setTimeout(function () {
      if (!cm.state.completionActive) {
        cm.showHint({
          completeSingle: false
        })
      }
    }, 100)
  }
  return CodeMirror.Pass
}

export default {
  name: 'codeEditor',
  props: {
    value: {
      type: String
    },
    placeholder: {
      type: String
    },
    option: {
      type: Object,
      default: () => {}
    }
  },
  data () {
    return {
      events: [
        'scroll',
        'changes',
        'beforeChange',
        'cursorActivity',
        'keyHandled',
        'inputRead',
        'electricInput',
        'beforeSelectionChange',
        'viewportChange',
        'swapDoc',
        'gutterClick',
        'gutterContextMenu',
        'focus',
        'blur',
        'refresh',
        'optionChange',
        'scrollCursorIntoView',
        'update'
      ],
      eventBinds: {}
    }
  },
  mounted () {
    const option = Object.assign({}, defaultOption, this.option)
    this.editor = CodeMirror.fromTextArea(this.$el, option)
    this.events.forEach((event) => {
      this.eventBinds[event] = this.callEvent.bind(this, event)
      this.editor.on(event, this.eventBinds[event])
    })
    this.setValue(this.value)
  },
  destroyed () {
    this.events.forEach((event) => {
      this.editor.off(event, this.eventBinds[event])
    })
    const parent = this.editor.doc.cm.getWrapperElement()
    parent && parent.remove && parent.remove()
  },
  watch: {
    value (newVal) {
      this.setValue(newVal)
    },
    option: {
      deeep: true,
      handler (options, oldOptions) {
        this.setOption(options)
      }
    }
  },
  methods: {
    setValue (val) {
      const value = this.editor.getValue()
      if (val === value) return
      const char = this.editor.doc.getCursor()
      this.editor.doc.setValue(val)
      this.editor.doc.setCursor(char)
    },
    setOption (options) {
      Object.keys(options).forEach((key) => {
        this.editor.setOption(key, options[key])
      })
    },
    callEvent (event, ...args) {
      if (event === 'changes') {
        this.$emit('input', args[0].getValue())
      }
      this.$emit(event, ...args)
    }
  }
}
</script>
